Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit c174d4af49498c1b2e737bfe5e9fd427931fea97


Parents : 9d2a19a
Author : Sudo-Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-01-01T16:19:18-06:00

feat(demo): add read-only demo dockerfile and nginx api mocking.

Changes

3 files changed, 227 insertions(+), 0 deletions(-)


Diff

diff --git a/Dockerfile.demo b/Dockerfile.demo
new file mode 100644
index 00000000..9221ca8d
--- /dev/null
+++ b/Dockerfile.demo
@@ -0,0 +1,30 @@
+# Build arguments
+ARG NODE_VERSION=20
+ARG NODE_ALPINE_SHA256=sha256:6a91081a440be0b57336fbc4ee87f3dab1a2fd6f80cdb355dcf960e13bda3b59
+
+# Build the frontend
+FROM node:${NODE_VERSION}-alpine@${NODE_ALPINE_SHA256} AS build-frontend
+
+WORKDIR /src
+
+COPY package.json vite.config.js pnpm-lock.yaml tailwind.config.js postcss.config.js ./
+COPY meshchatx ./meshchatx
+
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+RUN pnpm install
+
+RUN pnpm run build-frontend
+
+RUN find /src/meshchatx/public -type d -exec chmod 755 {} + && \
+ find /src/meshchatx/public -type f -exec chmod 644 {} +
+
+# Runtime stage
+FROM nginxinc/nginx-unprivileged:alpine
+
+COPY --from=build-frontend --chown=101:101 /src/meshchatx/public /usr/share/nginx/html
+COPY nginx.demo.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 8080
+
+CMD ["nginx", "-g", "daemon off;"]

diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml
new file mode 100644
index 00000000..6396ac79
--- /dev/null
+++ b/docker-compose.demo.yml
@@ -0,0 +1,29 @@
+services:
+ meshchatx-demo:
+ build:
+ context: .
+ dockerfile: Dockerfile.demo
+ image: ${MESHCHAT_DEMO_IMAGE:-reticulum-meshchatx-demo:local}
+ container_name: reticulum-meshchatx-demo
+ restart: unless-stopped
+ ports:
+ - "8080:8080"
+
+ # Security Hardening
+ security_opt:
+ - no-new-privileges:true
+ read_only: true
+ tmpfs:
+ - /tmp:mode=1777
+ - /var/cache/nginx:mode=1777
+ - /var/run:mode=1777
+ cap_drop:
+ - ALL
+
+ # Resource Limits
+ deploy:
+ resources:
+ limits:
+ memory: 128M
+ reservations:
+ memory: 64M

diff --git a/nginx.demo.conf b/nginx.demo.conf
new file mode 100644
index 00000000..76c8c528
--- /dev/null
+++ b/nginx.demo.conf
@@ -0,0 +1,168 @@
+server {
+ listen 8080;
+ server_name localhost;
+
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Support for SPA routing
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Don't serve index.html for missing assets
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|wasm|json)$ {
+ try_files $uri =404;
+ add_header Cache-Control "public, max-age=31536000, immutable";
+ }
+
+ # Handle manifest.json specifically if needed
+ location = /manifest.json {
+ try_files $uri =404;
+ }
+
+ # Mock API responses for demo mode
+ location /api/v1/auth/status {
+ return 200 '{"auth_enabled": false, "authenticated": true}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/config {
+ return 200 '{"config": {"theme": "dark", "language": "en", "display_name": "Demo User", "identity_hash": "effe95b0482d7b6c04844977d17e0c36", "lxmf_address_hash": "c0ffee00000000000000000000000000", "auto_announce_interval_seconds": 0, "lxmf_user_icon_name": "account", "lxmf_user_icon_foreground_colour": "#ffffff", "lxmf_user_icon_background_colour": "#000000"}}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/app/info {
+ return 200 '{"app_info": {"version": "demo", "rns_version": "0.0.0", "lxmf_version": "0.0.0", "python_version": "3.11", "is_connected_to_shared_instance": false, "is_transport_enabled": false, "reticulum_config_path": "/demo/rns.conf", "database_path": "/demo/meshchat.db", "database_file_size": 1048576, "is_demo": true, "memory_usage": {"rss": 52428800, "vms": 104857600}, "network_stats": {"bytes_sent": 102400, "bytes_recv": 204800, "packets_sent": 150, "packets_recv": 300}, "reticulum_stats": {"total_paths": 42, "announces_per_second": 0.1, "announces_per_minute": 6, "announces_per_hour": 360}}}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/database/health {
+ return 200 '{"database": {"quick_check": "ok", "journal_mode": "wal", "wal_autocheckpoint": 1000, "page_size": 4096, "page_count": 256, "freelist_pages": 10, "estimated_free_bytes": 40960}}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/reticulum/interfaces {
+ return 200 '{"interfaces": {
+ "RNode-LoRa-V3": {"type": "RNodeInterface", "enabled": "true", "port": "/dev/ttyUSB0", "frequency": 868000000, "bandwidth": 125000, "spreadingfactor": 7, "codingrate": 5, "txpower": 7},
+ "Internet": {"type": "TCPClientInterface", "enabled": "true", "target_host": "hub.reticulum.network", "target_port": 4242},
+ "I2C-Bus-0": {"type": "I2CInterface", "enabled": "false"},
+ "LAN Mesh": {"type": "UDPInterface", "enabled": "true", "listen_ip": "0.0.0.0", "listen_port": 4242, "forward_ip": "255.255.255.255", "forward_port": 4242}
+ }}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/interface-stats {
+ return 200 '{"interface_stats": {"interfaces": [
+ {"short_name": "RNode-LoRa-V3", "name": "AutoInterface[RNode]", "status": true, "bitrate": 1200, "txb": 1024, "rxb": 5120},
+ {"short_name": "Internet", "name": "AutoInterface[TCP]", "status": true, "bitrate": 10000000, "txb": 45678, "rxb": 987654},
+ {"short_name": "I2C-Bus-0", "name": "AutoInterface[I2C]", "status": false, "bitrate": 400000, "txb": 0, "rxb": 0},
+ {"short_name": "LAN Mesh", "name": "AutoInterface[UDP]", "status": true, "bitrate": 100000000, "txb": 12345, "rxb": 67890}
+ ]}}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/lxmf/conversations {
+ return 200 '{"conversations": [
+ {"destination_hash": "8b5cf6", "display_name": "Alice", "updated_at": "2026-01-01T12:00:00Z", "latest_message_preview": "Hello from the mesh!", "unread_count": 1, "lxmf_user_icon": {"icon_name": "account", "foreground_colour": "#ffffff", "background_colour": "#3b82f6"}},
+ {"destination_hash": "3b82f6", "display_name": "Bob", "updated_at": "2026-01-01T11:30:00Z", "latest_message_preview": "Did you see the new RNode update?", "unread_count": 0, "lxmf_user_icon": {"icon_name": "robot", "foreground_colour": "#ffffff", "background_colour": "#ef4444"}},
+ {"destination_hash": "10b981", "display_name": "Regional Hub", "updated_at": "2026-01-01T10:00:00Z", "latest_message_preview": "Status: All interfaces operational", "unread_count": 0, "lxmf_user_icon": {"icon_name": "server", "foreground_colour": "#ffffff", "background_colour": "#10b981"}}
+ ]}';
+ add_header Content-Type application/json;
+ }
+
+ location ~ ^/api/v1/lxmf-messages/conversation/(.*)$ {
+ return 200 '{"lxmf_messages": [
+ {"hash": "msg1", "source_hash": "8b5cf6", "destination_hash": "c0ffee00000000000000000000000000", "content": "Hey there!", "timestamp": 1735732500, "created_at": "2026-01-01T11:55:00Z", "state": "delivered", "method": "propagated"},
+ {"hash": "msg2", "source_hash": "8b5cf6", "destination_hash": "c0ffee00000000000000000000000000", "content": "Hello from the mesh!", "timestamp": 1735732800, "created_at": "2026-01-01T12:00:00Z", "state": "delivered", "method": "propagated"}
+ ]}';
+ add_header Content-Type application/json;
+ }
+
+ location ~ ^/api/v1/lxmf/conversations/(.*)/mark-as-read$ {
+ return 200 '{"success": true}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/blocked-destinations {
+ return 200 '{"blocked_destinations": []}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/telephone/status {
+ return 200 '{"active_call": null}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/lxmf/propagation-node/status {
+ return 200 '{"propagation_node_status": {"state": "idle", "connected": true, "address": "8b5cf6", "messages_received": 123, "messages_sent": 45}}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/announces {
+ return 200 '{"announces": [
+ {"destination_hash": "8b5cf6", "display_name": "Alice", "aspect": "lxmf.delivery", "updated_at": "2026-01-01T12:00:00Z", "hops": 1, "snr": 12.5},
+ {"destination_hash": "3b82f6", "display_name": "Bob", "aspect": "lxmf.delivery", "updated_at": "2026-01-01T11:30:00Z", "hops": 2, "snr": 8.2},
+ {"destination_hash": "10b981", "display_name": "Regional Hub", "aspect": "nomadnetwork.node", "updated_at": "2026-01-01T10:00:00Z", "hops": 1, "snr": 15.0},
+ {"destination_hash": "ef4444", "display_name": "Echo Bot", "aspect": "lxmf.delivery", "updated_at": "2026-01-01T09:00:00Z", "hops": 3},
+ {"destination_hash": "f68b5c", "display_name": "Nomad Portal", "aspect": "nomadnetwork.node", "updated_at": "2026-01-01T12:05:00Z", "hops": 1},
+ {"destination_hash": "a1b2c3", "display_name": "Charlie", "aspect": "lxmf.delivery", "updated_at": "2026-01-01T08:00:00Z", "hops": 4},
+ {"destination_hash": "d4e5f6", "display_name": "Mesh Gateway", "aspect": "nomadnetwork.node", "updated_at": "2026-01-01T12:10:00Z", "hops": 1}
+ ], "total_count": 7}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/path-table {
+ return 200 '{"path_table": [
+ {"hash": "8b5cf6", "hops": 1, "interface": "AutoInterface[RNode]"},
+ {"hash": "3b82f6", "hops": 2, "interface": "AutoInterface[TCP]"},
+ {"hash": "10b981", "hops": 1, "interface": "AutoInterface[RNode]"},
+ {"hash": "ef4444", "hops": 3, "interface": "AutoInterface[TCP]"},
+ {"hash": "f68b5c", "hops": 1, "interface": "AutoInterface[TCP]"},
+ {"hash": "a1b2c3", "hops": 4, "interface": "AutoInterface[UDP]"},
+ {"hash": "d4e5f6", "hops": 1, "interface": "AutoInterface[UDP]"}
+ ], "total_count": 7}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/notifications {
+ return 200 '{"notifications": [
+ {"id": 1, "type": "new_message", "title": "New Message", "content": "Alice: Hello from the mesh!", "created_at": "2026-01-01T12:00:00Z"}
+ ]}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/announce {
+ return 200 '{"message": "Announce sent (Demo Mode)"}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/lxmf/propagation-node/sync {
+ return 200 '{"message": "Sync started (Demo Mode)"}';
+ add_header Content-Type application/json;
+ }
+
+ location /api/v1/identity/backup/base32 {
+ return 200 '{"identity_base32": "demo_base32_identity_key_do_not_use"}';
+ add_header Content-Type application/json;
+ }
+
+ # Catch-all for other API calls
+ location /api {
+ return 404 '{"error": "Endpoint not available in demo mode"}';
+ add_header Content-Type application/json;
+ }
+
+ location /ws {
+ return 503;
+ }
+
+ # Error pages
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────